Load Libraries

#install.packages("dplyr")
#install.packages("stringr")
#install.packages("graphics")

# load installed libraries 
library(dplyr)
library(stringr)
library(graphics)
library(ggplot2)

Laden der Daten

Die Git-Historie liegt als .csv Datei vor. Diese wird mit den folgenden Befehlen geladen und anschließend automatisch zur weiteren Verarbeitung vorbereitet. Um einen kurzen Überblick zu verschaffen, wird ein kleiner Ausschnitt aus der importierten Datei gezeigt:

Dataframe erstellen

Es wird ein Dataframe aus den historischen Daten erstellt. Hierbei werden 2 neue Spalten hinzugefügt. Hierbei entspricht fileSize der Größe der momentanen Datei und commitCount entspricht die Anzahl der Commits die eine Datei erfahren hat.

df = db_git_history %>% 
  group_by(name,file) %>%
  arrange(name, file, timestamp) %>% 
  mutate(year = strftime(timestamp, "%Y")) %>%
  mutate(fileSize = cumsum(change)) %>%
  mutate(commitCount = n())

Korrelation berechnen

Im folgenden wird die Correlation zwischenCommitzahl(Aufwand) und LinesOfCode(Komplexität) pro Datei/File berechnet.

suppressWarnings({
  correlation_per_file = df %>% 
    group_by(name,file) %>%
    filter(commitCount > 20) %>%
    filter(100 < fileSize)  %>%
    summarize(correlation=cor(change,fileSize), fileSize = max(fileSize), commitCount = mean(commitCount)) %>%
    arrange(desc(correlation))
})

correlation_per_file$file <- reorder(correlation_per_file$file, correlation_per_file$correlation)

Plots

Plot Korrelation pro Datenbank zwischen Commitgröße und Filegröße, sortiert nach der Größe des Korrelationskoeffizients.

ggplot(correlation_per_file, aes(x=file, y=correlation, color = name, size = commitCount)) + geom_point(alpha=0.6) + facet_wrap(~ name)

ggplot(correlation_per_file, aes(x=file, y=correlation, color = name, size = fileSize)) + geom_point(alpha=0.6) + facet_wrap(~ name)

##Density Plot der Korrelation

ggplot(correlation_per_file, aes(x=correlation, color=name, fill=name)) +   
  geom_density(alpha=0.5) +
  geom_vline(aes(xintercept=mean(correlation, na.rm=TRUE)),color="blue", linetype="dashed", alpha=0.5, size=1)

ggplot(correlation_per_file, aes(x=correlation, color=name, fill=name)) +   
  geom_density(alpha=0.5) +
  geom_vline(aes(xintercept=mean(correlation, na.rm=TRUE)),color="blue", linetype="dashed", alpha=0.5, size=1) + facet_wrap(~name)

ggplot(correlation_per_file, aes(x=name, y=correlation, fill=name)) + 
  geom_boxplot() +
  theme(axis.text.x = element_text(size = 16, angle = 90, vjust = 0.5))

Berechnung der p-values

p_values = correlation_per_file %>% 
    distinct(name)

for (i in 1:20) {
  db_mean_sd = correlation_per_file %>% 
      group_by(name) %>%
      summarize(mean=mean(correlation,na.rm=TRUE), sd = sd(correlation, na.rm=TRUE))
  
  sampe_size = 50
  
  samples_mean_sd = correlation_per_file %>% 
    group_by(name) %>%
    sample_n(sampe_size,replace=TRUE) %>%
    summarize(sample_mean=mean(correlation,na.rm=TRUE), sample_sd = sd(correlation, na.rm=TRUE))
  
  mean_sd = db_mean_sd %>% inner_join(samples_mean_sd, by = "name")
  
  p_values_temp = mean_sd %>% 
    group_by(name) %>%
    mutate(z = (sample_mean - mean) / (sd/sqrt(sampe_size)), p_value = 2 * pnorm(-abs(z)))  %>% 
    select(name, p_value)

  p_values <- rbind(p_values, p_values_temp)
}

p_values = p_values %>% 
  group_by(name) %>%
  summarize(p_value=mean(p_value,na.rm=TRUE))

p_values

Bootstrap Plot der Korrelation

resamples = correlation_per_file %>% 
  group_by(name) %>%
  sample_n(1000,replace=TRUE)
ggplot(resamples, aes(x=correlation, color=name, fill=name)) +   
  geom_density(alpha=0.5) +
  geom_vline(aes(xintercept=mean(correlation, na.rm=TRUE)),color="blue", linetype="dashed", alpha=0.5, size=1) + facet_wrap(~name)

ggplot(resamples, aes(x=name, y=correlation, fill=name)) + 
  geom_boxplot() +
  theme(axis.text.x = element_text(size = 16, angle = 90, vjust = 0.5))

Korrelation über die Zeit

df_modified_for_correlation_over_time = df %>% 
  group_by(name, file, year) %>%
  summarize(change = sum(change), fileSize = last(fileSize), commitCount = mean(commitCount)) #%>%

cumcor <- function(x,y)  {
    sapply(seq_along(x), function(i) cor(x[1:i], y[1:i]))
}

suppressWarnings({
  correlation_over_time_per_file = df_modified_for_correlation_over_time %>% 
  group_by(name,file) %>%
  filter(commitCount > 20) %>%
  mutate(cum_cor=cumcor(change,fileSize)) %>%
  arrange(desc(name,file,year,cum_cor))
})
correlation_over_time = correlation_over_time_per_file %>% 
  group_by(name,year) %>%
  filter(cum_cor != "NA") %>%
  summarize(cum_cor_mean = mean(cum_cor)) #%>%
ggplot(correlation_over_time, aes(x=year, y=cum_cor_mean, color = name, group = 1)) + geom_line(size=1.5,alpha=0.6) + facet_wrap(~ name) + theme(axis.text.x = element_text(angle = 90))

ggplot(correlation_over_time, aes(x=cum_cor_mean, color=name, fill=name)) +   
  geom_density(alpha=0.5)

Top 50

file_size = correlation_per_file %>% 
  group_by(name,file) %>%
  arrange(desc(fileSize,correlation))
ggplot(data=file_size[1:50,], aes(x=file, y=correlation, fill = name)) +
  geom_bar(stat="identity",alpha=0.6) + 
  ggtitle("Plot of correlation of the biggest files") +
  theme(axis.text.x = element_text(size = 0, angle = 90, vjust = 0.5))

commit_count = correlation_per_file %>% 
  group_by(file) %>%
  arrange(desc(commitCount,correlation))
ggplot(data=commit_count[1:50,], aes(x=file, y=correlation, fill = name)) +
  geom_bar(stat="identity",alpha=0.6) + 
  ggtitle("Plot of correlation of the files with the most commits") +
  theme(axis.text.x = element_text(size = 16, angle = 90, vjust = 0.5))

LS0tDQp0aXRsZTogIkNvcnJlbGF0aW9uIGJldHdlZW4gY29tbWl0IGNvdW50IGFuZCBsaW5lcyBvZiBjb2RlIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBMb2FkIExpYnJhcmllcw0KYGBge3IsICB3YXJuaW5nPUZBTFNFLGluY2x1ZGU9IFRSVUUsZWNobz1UUlVFLHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFfQ0KI2luc3RhbGwucGFja2FnZXMoImRwbHlyIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJzdHJpbmdyIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJncmFwaGljcyIpDQoNCiMgbG9hZCBpbnN0YWxsZWQgbGlicmFyaWVzIA0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZ3JhcGhpY3MpDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KYGBge3IgZWNobyA9IEZBTFNFfQ0Kc2V0d2QoIks6L0RhdGEgU2NpZW5jZS9CRUdHRUwtRFMtV1MxOS1UMS1HSVQvIikNCmBgYA0KDQojIyMgTGFkZW4gZGVyIERhdGVuDQoNCkRpZSBHaXQtSGlzdG9yaWUgbGllZ3QgYWxzIC5jc3YgRGF0ZWkgdm9yLiBEaWVzZSB3aXJkIG1pdCBkZW4gZm9sZ2VuZGVuIEJlZmVobGVuIGdlbGFkZW4gdW5kIGFuc2NobGllw59lbmQgYXV0b21hdGlzY2ggenVyIHdlaXRlcmVuIFZlcmFyYmVpdHVuZyB2b3JiZXJlaXRldC4gVW0gZWluZW4ga3VyemVuIMOcYmVyYmxpY2sgenUgdmVyc2NoYWZmZW4sIHdpcmQgZWluIGtsZWluZXIgQXVzc2Nobml0dCBhdXMgZGVyIGltcG9ydGllcnRlbiBEYXRlaSBnZXplaWd0Og0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCg0KI0xvYWQgZGJfaW5mbyAuY3N2IGZpbGUNCiMgc291cmNlKCIuLi9jc3ZfZmlsZV9pbXBvcnQvbG9hZF9kYl9pbmZvLlIiKQ0KIyBkYl9pbmZvcyA9IGxvYWRfZGJfaW5mb3MoKQ0KDQojIHNvdXJjZSgiLi4vY3N2X2ZpbGVfaW1wb3J0L2xvYWRfZGJfaGlzdG9yeTIuUiIpDQojIGRiX2dpdF9oaXN0b3J5ID0gbG9hZF9kYl9oaXN0b3J5MigiLi4vLi4vV29ya3NwYWNlL2hpc3Rvcmllcy9Nb25nb0RCLmNzdiIpDQoNCiNMb2FkcyBhbGwgLmNzdiBnaXQgaGlzdG9yeSBmaWxlcyBhdmFpbGFibGUgaW4gLi93b3Jrc3BhY2UvaGlzdG9yaWVzDQpzb3VyY2UoIi4uL2Nzdl9maWxlX2ltcG9ydC9sb2FkX2RiX2hpc3RvcnlfYWxsLlIiKQ0KZGJfZ2l0X2hpc3RvcnkgPSBsb2FkX2RiX2hpc3RvcnlfYWxsKCkNCmBgYA0KDQpgYGB7ciBlY2hvID0gRkFMU0V9DQpoZWFkKGRiX2dpdF9oaXN0b3J5LCBuID0gMTApDQpgYGANCg0KIyMjIERhdGFmcmFtZSBlcnN0ZWxsZW4NCg0KRXMgd2lyZCBlaW4gRGF0YWZyYW1lIGF1cyBkZW4gaGlzdG9yaXNjaGVuIERhdGVuIGVyc3RlbGx0LiBIaWVyYmVpIHdlcmRlbiAyIG5ldWUgU3BhbHRlbiBoaW56dWdlZsO8Z3QuIEhpZXJiZWkgZW50c3ByaWNodCBmaWxlU2l6ZSBkZXIgR3LDtsOfZSBkZXIgbW9tZW50YW5lbiBEYXRlaSB1bmQgY29tbWl0Q291bnQgZW50c3ByaWNodCBkaWUgQW56YWhsIGRlciBDb21taXRzIGRpZSBlaW5lIERhdGVpIGVyZmFocmVuIGhhdC4NCg0KYGBge3J9DQpkZiA9IGRiX2dpdF9oaXN0b3J5ICU+JSANCiAgZ3JvdXBfYnkobmFtZSxmaWxlKSAlPiUNCiAgYXJyYW5nZShuYW1lLCBmaWxlLCB0aW1lc3RhbXApICU+JSANCiAgbXV0YXRlKHllYXIgPSBzdHJmdGltZSh0aW1lc3RhbXAsICIlWSIpKSAlPiUNCiAgbXV0YXRlKGZpbGVTaXplID0gY3Vtc3VtKGNoYW5nZSkpICU+JQ0KICBtdXRhdGUoY29tbWl0Q291bnQgPSBuKCkpDQpgYGANCg0KYGBge3IgZWNobyA9IEZBTFNFfQ0KaGVhZChkZiwgbiA9IDEwKQ0KYGBgDQoNCiMjIyBLb3JyZWxhdGlvbiBiZXJlY2huZW4NCg0KSW0gZm9sZ2VuZGVuIHdpcmQgZGllIENvcnJlbGF0aW9uIHp3aXNjaGVuQ29tbWl0emFobChBdWZ3YW5kKSB1bmQgTGluZXNPZkNvZGUoS29tcGxleGl0w6R0KSBwcm8gRGF0ZWkvRmlsZSBiZXJlY2huZXQuDQoNCmBgYHtyfQ0Kc3VwcHJlc3NXYXJuaW5ncyh7DQogIGNvcnJlbGF0aW9uX3Blcl9maWxlID0gZGYgJT4lIA0KICAgIGdyb3VwX2J5KG5hbWUsZmlsZSkgJT4lDQogICAgZmlsdGVyKGNvbW1pdENvdW50ID4gMjApICU+JQ0KICAgIGZpbHRlcigxMDAgPCBmaWxlU2l6ZSkgICU+JQ0KICAgIHN1bW1hcml6ZShjb3JyZWxhdGlvbj1jb3IoY2hhbmdlLGZpbGVTaXplKSwgZmlsZVNpemUgPSBtYXgoZmlsZVNpemUpLCBjb21taXRDb3VudCA9IG1lYW4oY29tbWl0Q291bnQpKSAlPiUNCiAgICBhcnJhbmdlKGRlc2MoY29ycmVsYXRpb24pKQ0KfSkNCg0KY29ycmVsYXRpb25fcGVyX2ZpbGUkZmlsZSA8LSByZW9yZGVyKGNvcnJlbGF0aW9uX3Blcl9maWxlJGZpbGUsIGNvcnJlbGF0aW9uX3Blcl9maWxlJGNvcnJlbGF0aW9uKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBGQUxTRX0NCmhlYWQoY29ycmVsYXRpb25fcGVyX2ZpbGUsIG4gPSAxMCkNCmBgYA0KDQojIyBQbG90cw0KDQojIyMgUGxvdCBLb3JyZWxhdGlvbiBwcm8gRGF0ZW5iYW5rIHp3aXNjaGVuIENvbW1pdGdyw7bDn2UgdW5kIEZpbGVncsO2w59lLCBzb3J0aWVydCBuYWNoIGRlciBHcsO2w59lIGRlcyBLb3JyZWxhdGlvbnNrb2VmZml6aWVudHMuIA0KDQpgYGB7ciBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9DQpnZ3Bsb3QoY29ycmVsYXRpb25fcGVyX2ZpbGUsIGFlcyh4PWZpbGUsIHk9Y29ycmVsYXRpb24sIGNvbG9yID0gbmFtZSwgc2l6ZSA9IGNvbW1pdENvdW50KSkgKyBnZW9tX3BvaW50KGFscGhhPTAuNikgKyBmYWNldF93cmFwKH4gbmFtZSkNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9DQpnZ3Bsb3QoY29ycmVsYXRpb25fcGVyX2ZpbGUsIGFlcyh4PWZpbGUsIHk9Y29ycmVsYXRpb24sIGNvbG9yID0gbmFtZSwgc2l6ZSA9IGZpbGVTaXplKSkgKyBnZW9tX3BvaW50KGFscGhhPTAuNikgKyBmYWNldF93cmFwKH4gbmFtZSkNCmBgYA0KDQojI0RlbnNpdHkgUGxvdCBkZXIgS29ycmVsYXRpb24NCg0KYGBge3IgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQ0KZ2dwbG90KGNvcnJlbGF0aW9uX3Blcl9maWxlLCBhZXMoeD1jb3JyZWxhdGlvbiwgY29sb3I9bmFtZSwgZmlsbD1uYW1lKSkgKyAgIA0KICBnZW9tX2RlbnNpdHkoYWxwaGE9MC41KSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihjb3JyZWxhdGlvbiwgbmEucm09VFJVRSkpLGNvbG9yPSJibHVlIiwgbGluZXR5cGU9ImRhc2hlZCIsIGFscGhhPTAuNSwgc2l6ZT0xKQ0KYGBgIA0KIA0KYGBge3IgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQ0KZ2dwbG90KGNvcnJlbGF0aW9uX3Blcl9maWxlLCBhZXMoeD1jb3JyZWxhdGlvbiwgY29sb3I9bmFtZSwgZmlsbD1uYW1lKSkgKyAgIA0KICBnZW9tX2RlbnNpdHkoYWxwaGE9MC41KSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihjb3JyZWxhdGlvbiwgbmEucm09VFJVRSkpLGNvbG9yPSJibHVlIiwgbGluZXR5cGU9ImRhc2hlZCIsIGFscGhhPTAuNSwgc2l6ZT0xKSArIGZhY2V0X3dyYXAofm5hbWUpDQpgYGAgDQoNCmBgYHtyIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0NCmdncGxvdChjb3JyZWxhdGlvbl9wZXJfZmlsZSwgYWVzKHg9bmFtZSwgeT1jb3JyZWxhdGlvbiwgZmlsbD1uYW1lKSkgKyANCiAgZ2VvbV9ib3hwbG90KCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41KSkNCmBgYCANCg0KIyMjIEJlcmVjaG51bmcgZGVyIHAtdmFsdWVzDQoNCmBgYHtyfQ0KcF92YWx1ZXMgPSBjb3JyZWxhdGlvbl9wZXJfZmlsZSAlPiUgDQogICAgZGlzdGluY3QobmFtZSkNCg0KZm9yIChpIGluIDE6MjApIHsNCiAgZGJfbWVhbl9zZCA9IGNvcnJlbGF0aW9uX3Blcl9maWxlICU+JSANCiAgICAgIGdyb3VwX2J5KG5hbWUpICU+JQ0KICAgICAgc3VtbWFyaXplKG1lYW49bWVhbihjb3JyZWxhdGlvbixuYS5ybT1UUlVFKSwgc2QgPSBzZChjb3JyZWxhdGlvbiwgbmEucm09VFJVRSkpDQogIA0KICBzYW1wZV9zaXplID0gNTANCiAgDQogIHNhbXBsZXNfbWVhbl9zZCA9IGNvcnJlbGF0aW9uX3Blcl9maWxlICU+JSANCiAgICBncm91cF9ieShuYW1lKSAlPiUNCiAgICBzYW1wbGVfbihzYW1wZV9zaXplLHJlcGxhY2U9VFJVRSkgJT4lDQogICAgc3VtbWFyaXplKHNhbXBsZV9tZWFuPW1lYW4oY29ycmVsYXRpb24sbmEucm09VFJVRSksIHNhbXBsZV9zZCA9IHNkKGNvcnJlbGF0aW9uLCBuYS5ybT1UUlVFKSkNCiAgDQogIG1lYW5fc2QgPSBkYl9tZWFuX3NkICU+JSBpbm5lcl9qb2luKHNhbXBsZXNfbWVhbl9zZCwgYnkgPSAibmFtZSIpDQogIA0KICBwX3ZhbHVlc190ZW1wID0gbWVhbl9zZCAlPiUgDQogICAgZ3JvdXBfYnkobmFtZSkgJT4lDQogICAgbXV0YXRlKHogPSAoc2FtcGxlX21lYW4gLSBtZWFuKSAvIChzZC9zcXJ0KHNhbXBlX3NpemUpKSwgcF92YWx1ZSA9IDIgKiBwbm9ybSgtYWJzKHopKSkgICU+JSANCiAgICBzZWxlY3QobmFtZSwgcF92YWx1ZSkNCg0KICBwX3ZhbHVlcyA8LSByYmluZChwX3ZhbHVlcywgcF92YWx1ZXNfdGVtcCkNCn0NCg0KcF92YWx1ZXMgPSBwX3ZhbHVlcyAlPiUgDQogIGdyb3VwX2J5KG5hbWUpICU+JQ0KICBzdW1tYXJpemUocF92YWx1ZT1tZWFuKHBfdmFsdWUsbmEucm09VFJVRSkpDQoNCnBfdmFsdWVzDQpgYGANCg0KIyMjIEJvb3RzdHJhcCBQbG90IGRlciBLb3JyZWxhdGlvbg0KDQoNCmBgYHtyIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0NCnJlc2FtcGxlcyA9IGNvcnJlbGF0aW9uX3Blcl9maWxlICU+JSANCiAgZ3JvdXBfYnkobmFtZSkgJT4lDQogIHNhbXBsZV9uKDEwMDAscmVwbGFjZT1UUlVFKQ0KYGBgIA0KDQpgYGB7ciBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9DQpnZ3Bsb3QocmVzYW1wbGVzLCBhZXMoeD1jb3JyZWxhdGlvbiwgY29sb3I9bmFtZSwgZmlsbD1uYW1lKSkgKyAgIA0KICBnZW9tX2RlbnNpdHkoYWxwaGE9MC41KSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihjb3JyZWxhdGlvbiwgbmEucm09VFJVRSkpLGNvbG9yPSJibHVlIiwgbGluZXR5cGU9ImRhc2hlZCIsIGFscGhhPTAuNSwgc2l6ZT0xKSArIGZhY2V0X3dyYXAofm5hbWUpDQpgYGAgDQoNCmBgYHtyIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0NCmdncGxvdChyZXNhbXBsZXMsIGFlcyh4PW5hbWUsIHk9Y29ycmVsYXRpb24sIGZpbGw9bmFtZSkpICsgDQogIGdlb21fYm94cGxvdCgpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2LCBhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSkpDQpgYGAgDQoNCiMjIyBLb3JyZWxhdGlvbiDDvGJlciBkaWUgWmVpdA0KDQpgYGB7cn0NCmRmX21vZGlmaWVkX2Zvcl9jb3JyZWxhdGlvbl9vdmVyX3RpbWUgPSBkZiAlPiUgDQogIGdyb3VwX2J5KG5hbWUsIGZpbGUsIHllYXIpICU+JQ0KICBzdW1tYXJpemUoY2hhbmdlID0gc3VtKGNoYW5nZSksIGZpbGVTaXplID0gbGFzdChmaWxlU2l6ZSksIGNvbW1pdENvdW50ID0gbWVhbihjb21taXRDb3VudCkpICMlPiUNCmBgYA0KDQpgYGB7ciBlY2hvID0gRkFMU0V9DQpoZWFkKGRmX21vZGlmaWVkX2Zvcl9jb3JyZWxhdGlvbl9vdmVyX3RpbWUsIG4gPSAxMCkNCmBgYA0KDQpgYGB7cn0NCg0KY3VtY29yIDwtIGZ1bmN0aW9uKHgseSkgIHsNCiAgICBzYXBwbHkoc2VxX2Fsb25nKHgpLCBmdW5jdGlvbihpKSBjb3IoeFsxOmldLCB5WzE6aV0pKQ0KfQ0KDQpzdXBwcmVzc1dhcm5pbmdzKHsNCiAgY29ycmVsYXRpb25fb3Zlcl90aW1lX3Blcl9maWxlID0gZGZfbW9kaWZpZWRfZm9yX2NvcnJlbGF0aW9uX292ZXJfdGltZSAlPiUgDQogIGdyb3VwX2J5KG5hbWUsZmlsZSkgJT4lDQogIGZpbHRlcihjb21taXRDb3VudCA+IDIwKSAlPiUNCiAgbXV0YXRlKGN1bV9jb3I9Y3VtY29yKGNoYW5nZSxmaWxlU2l6ZSkpICU+JQ0KICBhcnJhbmdlKGRlc2MobmFtZSxmaWxlLHllYXIsY3VtX2NvcikpDQp9KQ0KDQpgYGANCg0KYGBge3IgZWNobyA9IEZBTFNFfQ0KaGVhZChjb3JyZWxhdGlvbl9vdmVyX3RpbWVfcGVyX2ZpbGUsIG4gPSAxMCkNCmBgYA0KDQpgYGB7cn0NCmNvcnJlbGF0aW9uX292ZXJfdGltZSA9IGNvcnJlbGF0aW9uX292ZXJfdGltZV9wZXJfZmlsZSAlPiUgDQogIGdyb3VwX2J5KG5hbWUseWVhcikgJT4lDQogIGZpbHRlcihjdW1fY29yICE9ICJOQSIpICU+JQ0KICBzdW1tYXJpemUoY3VtX2Nvcl9tZWFuID0gbWVhbihjdW1fY29yKSkgIyU+JQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBGQUxTRX0NCmhlYWQoY29ycmVsYXRpb25fb3Zlcl90aW1lLCBuID0gMTApDQpgYGANCg0KYGBge3IgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQ0KZ2dwbG90KGNvcnJlbGF0aW9uX292ZXJfdGltZSwgYWVzKHg9eWVhciwgeT1jdW1fY29yX21lYW4sIGNvbG9yID0gbmFtZSwgZ3JvdXAgPSAxKSkgKyBnZW9tX2xpbmUoc2l6ZT0xLjUsYWxwaGE9MC42KSArIGZhY2V0X3dyYXAofiBuYW1lKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0NCmdncGxvdChjb3JyZWxhdGlvbl9vdmVyX3RpbWUsIGFlcyh4PWN1bV9jb3JfbWVhbiwgY29sb3I9bmFtZSwgZmlsbD1uYW1lKSkgKyAgIA0KICBnZW9tX2RlbnNpdHkoYWxwaGE9MC41KQ0KYGBgDQoNCiMjIyBUb3AgNTANCg0KYGBge3J9DQpmaWxlX3NpemUgPSBjb3JyZWxhdGlvbl9wZXJfZmlsZSAlPiUgDQogIGdyb3VwX2J5KG5hbWUsZmlsZSkgJT4lDQogIGFycmFuZ2UoZGVzYyhmaWxlU2l6ZSxjb3JyZWxhdGlvbikpDQpgYGANCg0KYGBge3IgZWNobyA9IEZBTFNFfQ0KaGVhZChmaWxlX3NpemUsIG4gPSAxMCkNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9DQpnZ3Bsb3QoZGF0YT1maWxlX3NpemVbMTo1MCxdLCBhZXMoeD1maWxlLCB5PWNvcnJlbGF0aW9uLCBmaWxsID0gbmFtZSkpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLGFscGhhPTAuNikgKyANCiAgZ2d0aXRsZSgiUGxvdCBvZiBjb3JyZWxhdGlvbiBvZiB0aGUgYmlnZ2VzdCBmaWxlcyIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDAsIGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQpgYGB7cn0NCmNvbW1pdF9jb3VudCA9IGNvcnJlbGF0aW9uX3Blcl9maWxlICU+JSANCiAgZ3JvdXBfYnkoZmlsZSkgJT4lDQogIGFycmFuZ2UoZGVzYyhjb21taXRDb3VudCxjb3JyZWxhdGlvbikpDQpgYGANCg0KYGBge3IgZWNobyA9IEZBTFNFfQ0KaGVhZChjb21taXRfY291bnQsIG4gPSAxMCkNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9DQpnZ3Bsb3QoZGF0YT1jb21taXRfY291bnRbMTo1MCxdLCBhZXMoeD1maWxlLCB5PWNvcnJlbGF0aW9uLCBmaWxsID0gbmFtZSkpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLGFscGhhPTAuNikgKyANCiAgZ2d0aXRsZSgiUGxvdCBvZiBjb3JyZWxhdGlvbiBvZiB0aGUgZmlsZXMgd2l0aCB0aGUgbW9zdCBjb21taXRzIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQoNCg0KDQoNCg0K